Resource
在设计 API 时,我们刻意隐藏了实现细节。作为一名 React 开发者,你只需要关注视图是什么样子,然后由 React 来处理如何来实现,所以我们不需要 React 开发者了解并发的底层运行原理。React v18.0 – React 中文文档
Pull requests · reactjs/rfcs · GitHub
Common Beginner Mistakes with React
Data binding in React: how to work with forms in React
Blogged Answers: A (Mostly) Complete Guide to React Rendering Behavior · Mark’s Dev Blog
Timeline of a React Component With Hooks | JulesBlom.com
组件
props
props 是组件的属性,通常用于从父组件向子组件传递数据。默认情况下,props 是只读的,这意味着子组件无法直接修改其接收到的 props。这种设计有助于保持数据流的单向性,使得组件的状态更加可控和可预测。
如果子组件需要修改 props 传递的数据,通常的做法是让父组件传递一个回调函数给子组件,在子组件内部调用这个回调函数来请求数据更改。然后,父组件可以相应地更新其状态,并重新渲染子组件,以传递更新后的 props。
Hooks
useState
你不能像这样把函数放入状态:
const [fn, setFn] = useState(someFunction);
function handleClick() { setFn(someOtherFunction);}
因为你传递了一个函数,React 认为 someFunction 是一个 初始化函数,而 someOtherFunction 是一个 更新函数,于是它尝试调用它们并存储结果。要实际 存储 一个函数,你必须在两种情况下在它们之前加上 () =>。然后 React 将存储你传递的函数。
初始化函数
如果将函数传递给
useState,React 仅在初始化期间调用它。
更新函数
它获取待定状态并从中计算下一个状态。
如果在同一事件中进行多个更新,则更新函数可能会有帮助。如果访问状态变量本身不方便(在优化重新渲染时可能会遇到这种情况),它们也很有用。
存储上一次的信息
在极少数情况下,你可能希望在响应渲染时调整状态——例如,当 props 改变时,你可能希望改变状态变量。
useEffect
在 Effect 中使用
fetch是 请求数据的一种流行方式,特别是在完全的客户端应用程序中。然而,这是一种非常手动的方法,而且有很大的缺点:
- Effect 不在服务器上运行。这意味着初始服务器渲染的 HTML 将只包含没有数据的 loading 状态。客户端电脑仅为了发现它现在需要加载数据,将不得不下载所有的脚本来渲染你的应用程序。这并不高效。
- 在 Effect 中直接请求数据很容易导致“网络瀑布”。当你渲染父组件时,它会请求一些数据,再渲染子组件,然后重复这样的过程来请求子组件的数据。如果网络不是很快,这将比并行请求所有数据要慢得多。
- 在 Effect 中直接请求数据通常意味着你不会预加载或缓存数据。例如,如果组件卸载后重新挂载,它不得不再次请求数据。
- 这不符合工效学。在调用
fetch时,需要编写大量样板代码,以避免像 竞争条件 这样的 bug。
依赖项为空数组的 Effect 不会在组件任何的 props 或 state 发生改变时重新运行。
useRef
使用 ref 可以确保:
- 可以在重新渲染之间 存储信息(普通对象存储的值每次渲染都会重置)。
- 改变它 不会触发重新渲染(状态变量会触发重新渲染)。
- 对于组件的每个副本而言,这些信息都是本地的(外部变量则是共享的)。
ref 不适合用于存储期望显示在屏幕上的信息。如有需要,使用 state 代替。
可以在 事件处理程序或者 Effect 中读取和写入 ref。
通常情况下,在渲染过程中写入或读取
ref.current是不允许的。然而,在这种情况下是可以的,因为结果总是一样的,而且条件只在初始化时执行,所以是完全可预测的。
useCallback
useCallback是一个允许你在多次渲染中缓存函数的 React Hook。
useCallback不会阻止创建函数。你总是在创建一个函数(这很好!),但是如果没有任何东西改变,React 会忽略它并返回缓存的函数。
使用
useCallback缓存函数仅在少数情况下有意义:
- 将其作为 props 传递给包装在 [
memo] 中的组件。如果 props 未更改,则希望跳过重新渲染。缓存允许组件仅在依赖项更改时重新渲染。 - 传递的函数可能作为某些 Hook 的依赖。比如,另一个包裹在
useCallback中的函数依赖于它,或者依赖于useEffect中的函数。
useCallback 和 memo
useMemo
useMemo是一个 React Hook,它在每次重新渲染的时候能够缓存计算的结果。
useMemo在多次重新渲染中缓存了函数计算的结果直到依赖项的值发生变化。
使用
useMemo进行优化仅在少数情况下有价值:
- 你在
useMemo中进行的计算明显很慢,而且它的依赖关系很少改变。 - 将计算结果作为 props 传递给包裹在
memo中的组件。当计算结果没有改变时,你会想跳过重新渲染。记忆化让组件仅在依赖项不同时才重新渲染。 - 你传递的值稍后用作某些 Hook 的依赖项。例如,也许另一个
useMemo计算值依赖它,或者useEffect依赖这个值。
第二点本质上就是 useCallback
useCallback 和 useMemo
-
联系:
- 二者都接收一个依赖项数组,用于决定何时重新计算或重新创建。
- 二者都用于性能优化,避免不必要的重复计算或重新渲染。
-
区别:
useCallback返回一个 memoized 的回调函数,用于缓存函数引用。useMemo返回一个 memoized 的值,用于缓存计算结果。
useContext
Usage
Components
Suspense
<Suspense>lets you display a fallback until its children have finished loading.
API
memo
memo(Component, arePropsEqual?)
使用
memo将组件包装起来,以获得该组件的一个 记忆化 版本。通常情况下,只要该组件的 props 没有改变,这个记忆化版本就不会在其父组件重新渲染时重新渲染。但 React 仍可能会重新渲染它:记忆化是一种性能优化,而非保证。
参数
可选参数
arePropsEqual:一个函数,接受两个参数:组件的前一个 props 和新的 props。如果旧的和新的 props 相等,即组件使用新的 props 渲染的输出和表现与旧的 props 完全相同,则它应该返回true。否则返回false。通常情况下,你不需要指定此函数。默认情况下,React 将使用Object.is比较每个 prop。
这个比较函数似乎与 arr.sort(fn)
返回值
memo 返回一个新的 React 组件。它的行为与提供给 memo 的组件相同,只是当它的父组件重新渲染时 React 不会总是重新渲染它,除非它的 props 发生了变化。
应用
即使使用 memo,如果它自己的
state或正在使用的context发生更改,组件也会重新渲染。
如果每次父组件重新渲染时创建一个新的对象或数组,即使它们每个元素都相同,React 仍会认为它已更改。同样地,如果在渲染父组件时创建一个新的函数,即使该函数具有相同的定义,React 也会认为它已更改。
如果 props 是一个对象,可以使用
useMemo避免父组件每次都重新创建该对象
memo 和 useCallback
虽然 memo 和 useCallback 都可以用于优化组件的性能,但它们的作用略有不同,适用于不同的场景。
- React. memo:
React.memo是一个高阶组件,用于包裹函数组件并缓存其渲染结果。它可以将组件的 props 进行浅比较,并在 props 没有变化时阻止不必要的重新渲染。- 当父组件重新渲染时,React. memo 会比较当前组件的 props 和上一次渲染时的 props。如果两者相等,则 React. memo 会跳过当前组件的重新渲染,直接返回上一次的渲染结果。
- React. memo 适用于函数组件的渲染结果会完全根据其 props 决定的情况,这样可以避免在 props 没有变化时进行不必要的重新渲染。
const MemoizedComponent = React.memo(MyComponent);
- useCallback:
useCallback用于缓存函数,它接收一个函数和一个依赖项数组,并返回一个 memoized 的函数。它的主要作用是在依赖项未变化时返回上一次缓存的函数引用,从而避免在父组件重新渲染时重新创建函数。- 当父组件重新渲染时,由于函数引用没有发生变化,子组件可以避免不必要的重新渲染,因为它们使用的是相同的回调函数引用。
const memoizedCallback = useCallback(() => { // Callback logic }, [/* 依赖项数组 */]);
总结:
React.memo适用于函数组件的渲染结果完全由 props 决定的情况,可以帮助组件在 props 没有变化时跳过重新渲染。useCallback适用于缓存函数,可以确保在依赖项未变化时返回相同的函数引用,从而避免在父组件重新渲染时重新创建函数。
在某些情况下,你可能需要同时使用 React.memo 和 useCallback 来最大程度地优化组件的性能。
在某些情况下,如果 props 中包含了引用类型(如对象或数组),浅比较可能会导致不准确的结果。这时候,你可能需要手动使用
useMemo或useCallback来优化性能,确保正确比较引用类型的变化。但对于大多数情况,React.memo可以很好地帮助你避免不必要的重新渲染,而无需手动指定依赖项数组。
forwardRef
默认情况下,每个组件的 DOM 节点都是私有的。然而,有时候将 DOM 节点公开给父组件是很有用的
- 使用
forwardRef包裹需要公开DOM的子组件, 使其能够接受ref参数, 并在子组件的内部某个节点绑定DOM到ref上 - 父组件使用
useRef声明一个null的ref,然后将ref传递给子组件 - 子组件将接受到的
ref转发forward给子组件内容绑定了ref的某个DOM节点/标签 - 父组件可以访问子组件内部某个
DOM节点并对其调用方法, 例如focus()。
将组件内部的 ref 暴露给 DOM 节点会使得在稍后更改组件内部更加困难。通常会暴露可重用的低级组件的 DOM 节点,例如按钮或文本输入框,但不会在应用程序级别的组件中这样做,例如头像或评论。
lazy
lazy(load)
参数
load
一个返回 Promise 或另一个 thenable(具有
then方法的类 Promise 对象)的函数。
返回的 Promise 和 Promise 的解析值都将被缓存,因此 React 不会多次调用 load 函数。如果 Promise 被拒绝,则 React 将抛出拒绝原因给最近的错误边界处理。
返回值
你需要返回一个 Promise 或其他 thenable(具有 then 方法的类 Promise 对象)。它最终需要解析为有效的 React 组件类型,例如函数、memo 或 forwardRef 组件。
Usage
| API | Usage |
|---|---|
memo | 889 k |
forwardRef | 327 k |
createContext | 147 k |
lazy | 125 k |
startTransition | 8 k |
渲染
条件渲染
事件处理
#Todo react props 只读不改